/*
 *  testClassFixedMemAlloc.cpp
 *  kangmodb
 *
 *  Created by 강모 김 on 11. 5. 10..
 *  Copyright 2011 강모소프트. All rights reserved.
 *
 */

#include "gtest/gtest.h"

#include "testCommon.h"
#include "types.h"
#include "kdInfra.h"
#include "memAlloc.h"


typedef struct testItem
{
	char name[20];
	int age;
} testItem;

const int MEM_CHUNK_SIZE = 512;

/** @brief Test kdFixedMemAlloc class.
 */
class FixedMemAllocTest : public testing::Test {
protected:  // You should make the members protected s.t. they can be
	// accessed from sub-classes.
	
	// virtual void SetUp() will be called before each test is run.  You
	// should define it if you need to initialize the varaibles.
	// Otherwise, this can be skipped.
	virtual void SetUp() {
		memChunk_ = new char[MEM_CHUNK_SIZE*2];
		alignedMemChunkAddr_ = kdMemAlign(memChunk_, MEM_CHUNK_SIZE);
		
		// Make sure the aligned memory is within the allocated area.
		ASSERT_TRUE( ((char*)alignedMemChunkAddr_ + MEM_CHUNK_SIZE) <= ((char*)memChunk_ + (MEM_CHUNK_SIZE*2)) );
		ASSERT_TRUE( kdFixedMemAlloc::create( alignedMemChunkAddr_, MEM_CHUNK_SIZE, sizeof(testItem), &alloc_) == 0 );
	}
	
	// virtual void TearDown() will be called after each test is run.
	// You should define it if there is cleanup work to do.  Otherwise,
	// you don't have to provide it.
	//
	virtual void TearDown() {
		ASSERT_TRUE( kdFixedMemAlloc::destroy( alloc_ ) == 0);
		
		alloc_ = NULL;
	}
protected:
	void * memChunk_;
	void * alignedMemChunkAddr_;
	kdFixedMemAlloc * alloc_;
};

/** @brief Test create function with a chunk whose size is not power of two.
 */
TEST_F(FixedMemAllocTest, create_chunk_size_not_power_of_two) {
	// A Chunk size which is not power of two
	const int UNALIGNED_CHUNK_SIZE=100;
	char chunk[UNALIGNED_CHUNK_SIZE*2];
	
	void * alignedChunkAddress = kdMemAlign(chunk, 64);
	
	// Should throw KD_EXCP_MEM_CHUNK_SIZE_NOT_POWER_OF_TWO
	ASSERT_TRUE( kdFixedMemAlloc::create( alignedChunkAddress, UNALIGNED_CHUNK_SIZE, sizeof(testItem), &alloc_) == KD_EXCP_MEM_CHUNK_SIZE_NOT_POWER_OF_TWO );
}


/** @brief Test create function with a chunk address which is not aligned to its size.
 */
TEST_F(FixedMemAllocTest, create_chunk_addr_not_aligned) {
	// A Chunk size which is not power of two
	const int CHUNK_SIZE=256;
	char chunk[CHUNK_SIZE*2];
	
	void * unalignedChunkAddress = ((char*) kdMemAlign(chunk, CHUNK_SIZE)) + CHUNK_SIZE/2; 
	
	// Should throw KD_EXCP_MEM_CHUNK_NOT_ALIGNED
	ASSERT_TRUE( kdFixedMemAlloc::create( unalignedChunkAddress, CHUNK_SIZE, sizeof(testItem), &alloc_) == KD_EXCP_MEM_CHUNK_NOT_ALIGNED );
}

/** @brief Test kdMemIsPowerOfTwo function.
 */
TEST_F(FixedMemAllocTest, kdMemGetAlignedBits) {
	uptr size = 1024;
	
	u32 alignedBits = kdMemGetAlignedBits(size);
	// Even after testing if the size is power of two, the size should not change.
	ASSERT_TRUE( size == 1024 );
	ASSERT_TRUE( alignedBits == 10 );
	
	ASSERT_TRUE( kdMemGetAlignedBits(1) == 0 );
	ASSERT_TRUE( kdMemGetAlignedBits(2) == 1);
	ASSERT_TRUE( kdMemGetAlignedBits(4) == 2);
	ASSERT_TRUE( kdMemGetAlignedBits(8) == 3);
	ASSERT_TRUE( kdMemGetAlignedBits(16) == 4);
	ASSERT_TRUE( kdMemGetAlignedBits(32) == 5);
	ASSERT_TRUE( kdMemGetAlignedBits(64) == 6);
	ASSERT_TRUE( kdMemGetAlignedBits(128) == 7);
	ASSERT_TRUE( kdMemGetAlignedBits(256) == 8);
	ASSERT_TRUE( kdMemGetAlignedBits(512) == 9);
	ASSERT_TRUE( kdMemGetAlignedBits(1024) == 10);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*2) == 11);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*4) == 12);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*8) == 13);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*16) == 14);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*32) == 15);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*64) == 16);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*128) == 17);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*256) == 18);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*512) == 19);
	ASSERT_TRUE( kdMemGetAlignedBits(1024*1024) == 20);
}

/** @brief Test kdMemIsPowerOfTwo function.
 */
TEST_F(FixedMemAllocTest, kdMemIsPowerOfTwo) {
	uptr size = 1024;
	
	bool isPowerOfTwo = kdMemIsPowerOfTwo(size);
	// Even after testing if the size is power of two, the size should not change.
	ASSERT_TRUE( size == 1024 );
	ASSERT_TRUE( isPowerOfTwo );
	
	ASSERT_TRUE( kdMemIsPowerOfTwo(1) ); // 1 is 2^0, so it is also power of two ;-).
	ASSERT_TRUE( kdMemIsPowerOfTwo(2) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(3) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(4) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(5) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(6) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(7) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(8) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(9) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(10) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(11) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(12) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(13) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(14) );
	ASSERT_FALSE( kdMemIsPowerOfTwo(15) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(16) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(32) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(64) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(128) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(256) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(512) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*2) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*4) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*8) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*16) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*32) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*64) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*128) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*256) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*512) );
	ASSERT_TRUE( kdMemIsPowerOfTwo(1024*1024) );
}

/** @brief Test if an address is aligned by 8 byte.
 */
TEST_F(FixedMemAllocTest, kdMemIsAlignedBy8Byte) {
	void* addr = (void*)1024;
	bool isAligned = kdMemIsAlignedBy8Byte((uptr)addr);
	// Even after calling kdMemIsPowerOfTwo, the size should not change.
	ASSERT_TRUE( addr == (void*)1024 );
	ASSERT_TRUE( isAligned );
	
	ASSERT_TRUE( kdMemIsAlignedBy8Byte((uptr)0) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)1) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)2) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)3) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)4) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)5) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)6) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)7) );
	ASSERT_TRUE( kdMemIsAlignedBy8Byte((uptr)8) );
	ASSERT_FALSE( kdMemIsAlignedBy8Byte((uptr)9) );
}

/** @brief Test aliging memory address by power of 2.
 * "void *" version uses "uptr" version, so we run the test on "void *" version.
 */
TEST_F(FixedMemAllocTest, kdMemAlign) {
	// Align by 2
	ASSERT_TRUE( kdMemAlign((void*)0,2) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)1,2) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)2,2) == (void*)2);
	ASSERT_TRUE( kdMemAlign((void*)3,2) == (void*)2);
	ASSERT_TRUE( kdMemAlign((void*)4,2) == (void*)4);
	// Align by 4
	ASSERT_TRUE( kdMemAlign((void*)0,4) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)1,4) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)2,4) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)3,4) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)4,4) == (void*)4);
	ASSERT_TRUE( kdMemAlign((void*)5,4) == (void*)4);
	// Align by 8
	for (uptr i=0; i<=7; i++)
		ASSERT_TRUE( kdMemAlign((void*)i,8) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)8,8) == (void*)8);
	ASSERT_TRUE( kdMemAlign((void*)9,8) == (void*)8);
	
	// Align by 1024
	ASSERT_TRUE( kdMemAlign((void*)1,1024) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)1023,1024) == (void*)0);
	ASSERT_TRUE( kdMemAlign((void*)1024,1024) == (void*)1024);
	ASSERT_TRUE( kdMemAlign((void*)1025,1024) == (void*)1024);
}

/** @brief Test aliging memory address by power of 2.
 * "void *" version uses "uptr" version, so we run the test on "void *" version.
 */
TEST_F(FixedMemAllocTest, kdMemAlignUp) {
	// Align by 2
	ASSERT_TRUE( kdMemAlignUp((void*)0,2) == (void*)0);
	ASSERT_TRUE( kdMemAlignUp((void*)1,2) == (void*)2);
	ASSERT_TRUE( kdMemAlignUp((void*)2,2) == (void*)2);
	ASSERT_TRUE( kdMemAlignUp((void*)3,2) == (void*)4);
	ASSERT_TRUE( kdMemAlignUp((void*)4,2) == (void*)4);
	// Align by 4
	ASSERT_TRUE( kdMemAlignUp((void*)0,4) == (void*)0);
	ASSERT_TRUE( kdMemAlignUp((void*)1,4) == (void*)4);
	ASSERT_TRUE( kdMemAlignUp((void*)2,4) == (void*)4);
	ASSERT_TRUE( kdMemAlignUp((void*)3,4) == (void*)4);
	ASSERT_TRUE( kdMemAlignUp((void*)4,4) == (void*)4);
	ASSERT_TRUE( kdMemAlignUp((void*)5,4) == (void*)8);
	// Align by 8
	ASSERT_TRUE( kdMemAlignUp((void*)0,8) == (void*)0);
	for (uptr i=1; i<=8; i++)
		ASSERT_TRUE( kdMemAlignUp((void*)i,8) == (void*)8);
	ASSERT_TRUE( kdMemAlignUp((void*)9,8) == (void*)16);
	
	// Align by 1024
	ASSERT_TRUE( kdMemAlignUp((void*)1,1024) == (void*)1024);
	ASSERT_TRUE( kdMemAlignUp((void*)1023,1024) == (void*)1024);
	ASSERT_TRUE( kdMemAlignUp((void*)1024,1024) == (void*)1024);
	ASSERT_TRUE( kdMemAlignUp((void*)1025,1024) == (void*)2048);
}


/** @brief Test aliging memory address by power of 2.
 */
TEST_F(FixedMemAllocTest, DISABLED_kdMemAlign_by_value_not_power_of_two) {
	// TODO : Hit Assertion if the size of alignment is not power of two.
}

/** @brief Test allocating invalid size of memory.
 */
TEST_F(FixedMemAllocTest, DISABLED_AllocInvalidSize) {
	// TODO : Hit Assertion if the size of memory is not sizeof(testItem)
}


/** @brief Test freeing invalid memory (not allocated by the kdFixedMemAlloc).
 */
TEST_F(FixedMemAllocTest, DISABLED_FreeInvalidMemory) {
	// TODO : Hit Assertion if the memory is not allocated by kdFixedMemAlloc.
}


/** @brief Test freeing a slot whose magic in the header is invalid.
 */
TEST_F(FixedMemAllocTest, DISABLED_InvalidSlotHeaderMagic) {
	// TODO : Hit Assertion if the magic is invalid.
}

/** @brief Test freeing a slot whose magic in the tail is invalid.
 */
TEST_F(FixedMemAllocTest, DISABLED_InvalidSlotTailMagic) {
	// TODO : Hit Assertion if the magic is invalid.
}


/** @brief Test accessing an allocator object whose magic in the header is invalid.
 */
TEST_F(FixedMemAllocTest, DISABLED_InvalidAllocatorHeaderMagic) {
	// TODO : Hit Assertion if the magic is invalid.
}

/** @brief Test accessing an allocator object whose magic in the tail is invalid.
 */
TEST_F(FixedMemAllocTest, DISABLED_InvalidAllocatorTailMagic) {
	// TODO : Hit Assertion if the magic is invalid.
}

/** @brief Test setting and validating the magic value on the slot header.
 */
TEST_F(FixedMemAllocTest, MagicOnSlotHeader) {
	kdMemHeader header;
	kdMemSetMagic( &header );
	ASSERT_TRUE( kdMemHasValidMagic( &header ) );
}

/** @brief Test getting and setting the allocated size.
 */
TEST_F(FixedMemAllocTest, AllocSizeOnSlotHeader) {
	kdMemHeader header;
	for (u32 size=8; size < 1024; size+=8)
	{
		kdMemSetAllocatedSize( &header, size );
		ASSERT_TRUE( kdMemGetAllocatedSize( &header ) == size );
	}
}

/** @brief Test getting the allocator object from the allocated slot header.
 */
TEST_F(FixedMemAllocTest, HelperFunctions) {
	const uptr CHUNK_SIZE= 256;
	
	char chunk[ CHUNK_SIZE * 2 ];
	void * alignedChunkAddr = kdMemAlign( chunk, CHUNK_SIZE );
	
	kdMemSetChunkMagic(alignedChunkAddr, CHUNK_SIZE);
	
	void * allocatorObject = kdMemGetAllocator(alignedChunkAddr);
	
	// An allocated slot is within CHUNK_SIZE starting from allocatorObject.
	kdMemHeader * header = (kdMemHeader*) allocatorObject + 0x20;
	kdMemSetMagic( header );
	ASSERT_TRUE( kdMemHasValidMagic( header ) );
	kdMemSetChunkSize( header, CHUNK_SIZE );
	ASSERT_TRUE( kdMemGetChunkSize( header ) == CHUNK_SIZE );
	kdMemSetAllocatedSize( header, 32 );
	ASSERT_TRUE( kdMemGetAllocatedSize( header ) == 32 );
	ASSERT_TRUE( kdMemGetChunkAddr( header ) == alignedChunkAddr);
	ASSERT_TRUE( kdMemGetAllocator( alignedChunkAddr ) == allocatorObject );
	ASSERT_TRUE( kdMemGetAllocator( header ) == allocatorObject );

	ASSERT_TRUE( kdMemChunkHasValidMagic(alignedChunkAddr, CHUNK_SIZE) );
}

/** @brief Test setting invalid alloc size on the slot header.
 */
TEST_F(FixedMemAllocTest, DISABLED_SetInvalidAllocSizeOnSlotHeadr) {
	// The allocated size of a slot should be aligned by 8 bytes. Hits an assertion if this condition is not satisfied.
}


/** @brief Test allocate all slots, free all slots, allocate all slots.
 */
TEST_F(FixedMemAllocTest, AllocFreeAllocAll) {
	testItem * items[512] = {NULL,};
	int allocCount;
	// Allocate all slots
	kdExcp ex;
	{
		for (allocCount=0; (ex=alloc_->alloc(sizeof(testItem), (void**)&items[allocCount])) == 0; allocCount++ )
		{
			// Write something to the allocated memory.
			memset( items[allocCount], 0x43, sizeof(testItem) );
		}
		ASSERT_TRUE( ex == KD_EXCP_CHUNK_MEM_OUT_OF_FREESLOTS );

		// It does not make sense that we can't allocate the number of slots we would've allocated on that the testItem size was doubled.
		ASSERT_TRUE( allocCount >= MEM_CHUNK_SIZE/(sizeof(testItem)*2) );
	}
	// Free all slots
	{
		for (int i=0; i<allocCount; i++)
		{
			// We don't need to know which allocator object we used. Simply call kdMemAlloc::freeWithAllocator on the allocated memory.
			ASSERT_TRUE( kdMemAlloc::freeWithAllocator( items[i] ) == 0 );
		}
	}
	// Allocate all slots again
	{
		int j;
		for (j=0; (ex=alloc_->alloc(sizeof(testItem), (void**)&items[j])) == 0; j++ )
		{
			// Write something to the allocated memory.
			memset( items[j], 0x43, sizeof(testItem) );
		}
		ASSERT_TRUE( ex == KD_EXCP_CHUNK_MEM_OUT_OF_FREESLOTS );
		// The number of slots allocated should be exactly same to the one we did before.
		ASSERT_TRUE( j == allocCount );
	}
	// Free all slots
	{
		for (int k=0; k<allocCount; k++)
		{
			// We don't need to know which allocator object we used. Simply call kdMemAlloc::freeWithAllocator on the allocated memory.
			ASSERT_TRUE( kdMemAlloc::freeWithAllocator( items[k] ) == 0 );
		}
	}
}